Skip to content

fix(build): don't block SSG on telemetry flush, add persistence spans to trace-build#91335

Merged
lukesandberg merged 12 commits intocanaryfrom
dont_block_ssg_on_telemetry
Mar 16, 2026
Merged

fix(build): don't block SSG on telemetry flush, add persistence spans to trace-build#91335
lukesandberg merged 12 commits intocanaryfrom
dont_block_ssg_on_telemetry

Conversation

@lukesandberg
Copy link
Contributor

@lukesandberg lukesandberg commented Mar 13, 2026

What?

Two fixes for the Turbopack build tracing introduced in #90397:

  1. Don't block SSG on Turbopack shutdown: workerMain() no longer awaits the shutdown promise before returning. Trace event collection is deferred to waitForShutdown(), which the parent process awaits after SSG completes. This allows static generation and Turbopack persistence/cache-flush to run in parallel.

  2. Add persistence spans to trace-build allowlist: turbopack-build-events, turbopack-persistence, and turbopack-compaction are now included in the to-json-build.ts allowlist so they appear in .next/trace-build.

Why?

  • The await shutdownPromise in workerMain() was too eager — it prevented the caller from acknowledging the build as complete and starting SSG until Turbopack persistence finished flushing to disk.
  • The persistence/compaction spans emitted by Rust (turbopack-persistence, turbopack-compaction) were not in the to-json-build.ts allowlist, so they were silently filtered out of .next/trace-build.

How?

impl.ts (worker):

  • Removed await shutdownPromise from workerMain() — it now returns build results immediately
  • waitForShutdown() now returns { debugTraceEvents } after awaiting shutdown, so trace events are collected only after all compilation events (including persistence spans) have been processed

index.ts (parent):

  • Moved recordTraceEvents(debugTraceEvents) from the workerMain result handler into the shutdownPromise .then() chain, so events are replayed into the parent reporter after shutdown completes

to-json-build.ts:

  • Added turbopack-build-events, turbopack-persistence, turbopack-compaction to the allowlist

Test updates:

  • Enabled turbopackFileSystemCacheForBuild: true in the trace-build test fixture
  • Updated the Turbopack inline snapshot to include turbopack-build-events

@nextjs-bot
Copy link
Collaborator

nextjs-bot commented Mar 13, 2026

Tests Passed

@lukesandberg lukesandberg force-pushed the dont_block_ssg_on_telemetry branch from fbb2dd4 to c3cd496 Compare March 13, 2026 19:46
@nextjs-bot
Copy link
Collaborator

nextjs-bot commented Mar 13, 2026

Stats from current PR

✅ No significant changes detected

📊 All Metrics
📖 Metrics Glossary

Dev Server Metrics:

  • Listen = TCP port starts accepting connections
  • First Request = HTTP server returns successful response
  • Cold = Fresh build (no cache)
  • Warm = With cached build artifacts

Build Metrics:

  • Fresh = Clean build (no .next directory)
  • Cached = With existing .next directory

Change Thresholds:

  • Time: Changes < 50ms AND < 10%, OR < 2% are insignificant
  • Size: Changes < 1KB AND < 1% are insignificant
  • All other changes are flagged to catch regressions

⚡ Dev Server

Metric Canary PR Change Trend
Cold (Listen) 1.067s 1.069s ▆█▁▁▁
Cold (Ready in log) 1.066s 1.055s ▄█▁▁▁
Cold (First Request) 2.304s 2.292s ▄█▁▁▁
Warm (Listen) 1.066s 1.065s ▆█▁▁▁
Warm (Ready in log) 1.076s 1.068s ▄█▁▁▁
Warm (First Request) 917ms 909ms ▃█▁▁▁
📦 Dev Server (Webpack) (Legacy)

📦 Dev Server (Webpack)

Metric Canary PR Change Trend
Cold (Listen) 455ms 456ms ▁▁▁▁▁
Cold (Ready in log) 436ms 436ms ▁▃▄▁▃
Cold (First Request) 1.870s 1.856s ▁▂▂▁▂
Warm (Listen) 455ms 455ms ▁▁▁▁▁
Warm (Ready in log) 437ms 436ms ▁▃▄▁▄
Warm (First Request) 1.879s 1.866s ▁▂▂▁▂

⚡ Production Builds

Metric Canary PR Change Trend
Fresh Build 9.349s 9.257s ▅█▁▁▁
Cached Build 9.330s 9.303s ▅█▂▁▁
📦 Production Builds (Webpack) (Legacy)

📦 Production Builds (Webpack)

Metric Canary PR Change Trend
Fresh Build 14.208s 14.193s ▁▁▁▁▁
Cached Build 14.431s 14.471s ▁▁▁▁▁
node_modules Size 483 MB 483 MB ▁████
📦 Bundle Sizes

Bundle Sizes

⚡ Turbopack

Client

Main Bundles
Canary PR Change
0-m3lxn9ivp_d.js gzip 162 B N/A -
0.-r71sd1j_zb.js gzip 7.61 kB N/A -
0~-plqowrxaiq.js gzip 170 B N/A -
0~lwfcrlb4v_9.css gzip 115 B 115 B
00h0nz7r436~l.js gzip 13.3 kB N/A -
029_0n_61aap-.js gzip 70.8 kB N/A -
02ku7edzc_wf7.js gzip 450 B N/A -
03~yq9q893hmn.js gzip 39.4 kB 39.4 kB
092lcb3fqrrf9.js gzip 8.52 kB N/A -
0ac.phskjau0b.js gzip 158 B N/A -
0aj~xs1l1g8tg.js gzip 8.53 kB N/A -
0g0avicw0a5.l.js gzip 156 B N/A -
0gcm6h40wgorw.js gzip 48.2 kB N/A -
0h~_p6c6krw3f.js gzip 155 B N/A -
0h35gmp9u328z.js gzip 8.54 kB N/A -
0h6fkavebp.iz.js gzip 8.47 kB N/A -
0ino_yf1k3h6k.js gzip 10.4 kB N/A -
0k.sixc712dq1.js gzip 10.1 kB N/A -
0kkm7tesfinr6.js gzip 12.9 kB N/A -
0l4bu6-iylytu.js gzip 156 B N/A -
0moy~uao4dl.m.js gzip 9.19 kB N/A -
0oxfh9vf9xol0.js gzip 156 B N/A -
0q50rtpusjy90.js gzip 2.28 kB N/A -
0smgy2grrrlka.js gzip 8.58 kB N/A -
0t1dzhdfh0txh.js gzip 215 B 215 B
0u2bvb3fwjc.j.js gzip 155 B N/A -
0vwpsns7_n8j5.js gzip 13.6 kB N/A -
0yedy2nlxl.ia.js gzip 162 B N/A -
0zid7o0-vupvp.js gzip 225 B N/A -
10p-bsrowjca0.js gzip 158 B N/A -
11s6x9pcj4vxt.js gzip 157 B N/A -
11yo3xfd6b147.js gzip 12.9 kB N/A -
13.84hqxl_1p7.js gzip 9.76 kB N/A -
14v2a7u4xu22z.js gzip 157 B N/A -
1554wr-t7p6z-.js gzip 8.55 kB N/A -
15s.go9_6chuz.js gzip 65.7 kB N/A -
15tjst79~qy3_.js gzip 1.46 kB N/A -
15z_v00ne4ud0.js gzip 8.47 kB N/A -
17d_m3p4j9w6r.js gzip 5.62 kB N/A -
17yu~3yiu7d2m.js gzip 8.52 kB N/A -
188gain_2u3k9.js gzip 157 B N/A -
turbopack-0...b8_0.js gzip 4.16 kB N/A -
turbopack-03..u6-n.js gzip 4.16 kB N/A -
turbopack-06...odu.js gzip 4.14 kB N/A -
turbopack-09..jw41.js gzip 4.16 kB N/A -
turbopack-09..i1z9.js gzip 4.16 kB N/A -
turbopack-0m..vxy3.js gzip 4.16 kB N/A -
turbopack-0m..~hqc.js gzip 4.15 kB N/A -
turbopack-0o..8~3x.js gzip 4.16 kB N/A -
turbopack-0q..mhg_.js gzip 4.16 kB N/A -
turbopack-0s..gliq.js gzip 4.16 kB N/A -
turbopack-0u..k9zu.js gzip 4.16 kB N/A -
turbopack-10..8sax.js gzip 4.15 kB N/A -
turbopack-11..4bv0.js gzip 4.16 kB N/A -
turbopack-15..tsj9.js gzip 4.17 kB N/A -
01umcdex4f~m9.js gzip N/A 13.6 kB -
02gzbp8wsyfo-.js gzip N/A 70.8 kB -
03t__~.5lvgeu.js gzip N/A 5.62 kB -
04d6ll75jqx3r.js gzip N/A 9.19 kB -
04yegd4pwdu3f.js gzip N/A 152 B -
05.z3nadwcjhm.js gzip N/A 155 B -
0508ye.nu_ii2.js gzip N/A 157 B -
05673jb9k5ren.js gzip N/A 157 B -
0583exyh-yhc7.js gzip N/A 9.76 kB -
05b07nana~4_..js gzip N/A 10.1 kB -
072lv63r8dcz~.js gzip N/A 8.58 kB -
076vw6tg50945.js gzip N/A 153 B -
08refq~-egobr.js gzip N/A 156 B -
09j2nyjh-gde8.js gzip N/A 158 B -
0ar1~bwpezfgw.js gzip N/A 13.3 kB -
0c3leaugxcvgb.js gzip N/A 155 B -
0c99mq1ez2bke.js gzip N/A 450 B -
0cq-cmde_ws6u.js gzip N/A 8.47 kB -
0fwf102w10o9~.js gzip N/A 8.52 kB -
0gtmn.q_j1v5r.js gzip N/A 10.4 kB -
0l4d~z7ixj3wz.js gzip N/A 12.9 kB -
0n7bzm036.uw3.js gzip N/A 160 B -
0nclq9z6yzzm5.js gzip N/A 1.46 kB -
0nvnmuxpdyn-t.js gzip N/A 65.7 kB -
0nzumcogektg7.js gzip N/A 8.55 kB -
0s.c-cn5eebrx.js gzip N/A 8.47 kB -
0s5ej0rhsf.3a.js gzip N/A 169 B -
0tna7lg6q4zne.js gzip N/A 12.9 kB -
0votdfxr5fb5u.js gzip N/A 2.28 kB -
0xkuhv202qqhu.js gzip N/A 7.6 kB -
0y~srtt-b14d..js gzip N/A 155 B -
0ykl9bs_qj.5..js gzip N/A 8.52 kB -
0zfen0tnxp4gh.js gzip N/A 8.55 kB -
10wkq1h9jzkg..js gzip N/A 225 B -
13ncdn~mslimg.js gzip N/A 162 B -
149ndfh8zfcaz.js gzip N/A 8.53 kB -
14baw4rjxv_ds.js gzip N/A 48.1 kB -
14ix9emhgsyxn.js gzip N/A 154 B -
turbopack-0_..zz7r.js gzip N/A 4.16 kB -
turbopack-0...0dw_.js gzip N/A 4.16 kB -
turbopack-0~..tg3z.js gzip N/A 4.16 kB -
turbopack-0~..azp4.js gzip N/A 4.16 kB -
turbopack-0c..2hn8.js gzip N/A 4.16 kB -
turbopack-0h..-gyh.js gzip N/A 4.16 kB -
turbopack-0i..np7y.js gzip N/A 4.14 kB -
turbopack-0p..~xt1.js gzip N/A 4.16 kB -
turbopack-0r..lj0f.js gzip N/A 4.16 kB -
turbopack-0v..mnsx.js gzip N/A 4.16 kB -
turbopack-0y..jxh8.js gzip N/A 4.16 kB -
turbopack-10..yp1k.js gzip N/A 4.16 kB -
turbopack-11..47ja.js gzip N/A 4.17 kB -
turbopack-16..~v78.js gzip N/A 4.16 kB -
Total 463 kB 462 kB ✅ -15 B

Server

Middleware
Canary PR Change
middleware-b..fest.js gzip 712 B 716 B
Total 712 B 716 B ⚠️ +4 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 426 B 429 B
Total 426 B 429 B ⚠️ +3 B

📦 Webpack

Client

Main Bundles
Canary PR Change
5528-HASH.js gzip 5.54 kB N/A -
6280-HASH.js gzip 60.1 kB N/A -
6335.HASH.js gzip 169 B N/A -
912-HASH.js gzip 4.59 kB N/A -
e8aec2e4-HASH.js gzip 62.7 kB N/A -
framework-HASH.js gzip 59.7 kB 59.7 kB
main-app-HASH.js gzip 256 B 254 B
main-HASH.js gzip 39.2 kB 39.2 kB
webpack-HASH.js gzip 1.68 kB 1.68 kB
262-HASH.js gzip N/A 4.59 kB -
2889.HASH.js gzip N/A 169 B -
5602-HASH.js gzip N/A 5.55 kB -
6948ada0-HASH.js gzip N/A 62.7 kB -
9544-HASH.js gzip N/A 60.8 kB -
Total 234 kB 235 kB ⚠️ +689 B
Polyfills
Canary PR Change
polyfills-HASH.js gzip 39.4 kB 39.4 kB
Total 39.4 kB 39.4 kB
Pages
Canary PR Change
_app-HASH.js gzip 194 B 194 B
_error-HASH.js gzip 183 B 180 B 🟢 3 B (-2%)
css-HASH.js gzip 331 B 330 B
dynamic-HASH.js gzip 1.81 kB 1.81 kB
edge-ssr-HASH.js gzip 256 B 256 B
head-HASH.js gzip 351 B 352 B
hooks-HASH.js gzip 384 B 383 B
image-HASH.js gzip 580 B 581 B
index-HASH.js gzip 260 B 260 B
link-HASH.js gzip 2.51 kB 2.51 kB
routerDirect..HASH.js gzip 320 B 319 B
script-HASH.js gzip 386 B 386 B
withRouter-HASH.js gzip 315 B 315 B
1afbb74e6ecf..834.css gzip 106 B 106 B
Total 7.98 kB 7.98 kB ✅ -1 B

Server

Edge SSR
Canary PR Change
edge-ssr.js gzip 125 kB 125 kB
page.js gzip 268 kB 267 kB
Total 393 kB 392 kB ✅ -304 B
Middleware
Canary PR Change
middleware-b..fest.js gzip 617 B 613 B
middleware-r..fest.js gzip 156 B 155 B
middleware.js gzip 43.8 kB 43.7 kB
edge-runtime..pack.js gzip 842 B 842 B
Total 45.5 kB 45.3 kB ✅ -165 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 715 B 718 B
Total 715 B 718 B ⚠️ +3 B
Build Cache
Canary PR Change
0.pack gzip 4.25 MB 4.25 MB
index.pack gzip 108 kB 109 kB
index.pack.old gzip 109 kB 109 kB
Total 4.47 MB 4.47 MB ✅ -1.74 kB

🔄 Shared (bundler-independent)

Runtimes
Canary PR Change
app-page-exp...dev.js gzip 332 kB 332 kB
app-page-exp..prod.js gzip 180 kB 180 kB
app-page-tur...dev.js gzip 332 kB 332 kB
app-page-tur..prod.js gzip 180 kB 180 kB
app-page-tur...dev.js gzip 328 kB 328 kB
app-page-tur..prod.js gzip 178 kB 178 kB
app-page.run...dev.js gzip 329 kB 329 kB
app-page.run..prod.js gzip 179 kB 179 kB
app-route-ex...dev.js gzip 76 kB 76 kB
app-route-ex..prod.js gzip 51.7 kB 51.7 kB
app-route-tu...dev.js gzip 76 kB 76 kB
app-route-tu..prod.js gzip 51.7 kB 51.7 kB
app-route-tu...dev.js gzip 75.6 kB 75.6 kB
app-route-tu..prod.js gzip 51.5 kB 51.5 kB
app-route.ru...dev.js gzip 75.5 kB 75.5 kB
app-route.ru..prod.js gzip 51.4 kB 51.4 kB
dist_client_...dev.js gzip 324 B 324 B
dist_client_...dev.js gzip 326 B 326 B
dist_client_...dev.js gzip 318 B 318 B
dist_client_...dev.js gzip 317 B 317 B
pages-api-tu...dev.js gzip 43.3 kB 43.3 kB
pages-api-tu..prod.js gzip 33 kB 33 kB
pages-api.ru...dev.js gzip 43.3 kB 43.3 kB
pages-api.ru..prod.js gzip 33 kB 33 kB
pages-turbo....dev.js gzip 52.7 kB 52.7 kB
pages-turbo...prod.js gzip 38.6 kB 38.6 kB
pages.runtim...dev.js gzip 52.7 kB 52.7 kB
pages.runtim..prod.js gzip 38.6 kB 38.6 kB
server.runti..prod.js gzip 62.4 kB 62.4 kB
Total 2.95 MB 2.95 MB ⚠️ +3 B
📝 Changed Files (2 files)

Files with changes:

  • pages-api.runtime.dev.js
  • pages.runtime.dev.js
View diffs
pages-api.runtime.dev.js

Diff too large to display

pages.runtime.dev.js

Diff too large to display

📎 Tarball URL
https://vercel-packages.vercel.app/next/commits/77ececa546313e2428aca15b496395b39166c61c/next

@nextjs-bot nextjs-bot added the Turbopack Related to Turbopack with Next.js. label Mar 13, 2026
@codspeed-hq
Copy link

codspeed-hq bot commented Mar 13, 2026

Merging this PR will not alter performance

✅ 17 untouched benchmarks
⏩ 3 skipped benchmarks1


Comparing dont_block_ssg_on_telemetry (77ececa) with canary (4a99afc)

Open in CodSpeed

Footnotes

  1. 3 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@lukesandberg lukesandberg marked this pull request as ready for review March 14, 2026 23:33
@lukesandberg lukesandberg requested a review from sokra March 14, 2026 23:33
lukesandberg and others added 10 commits March 15, 2026 15:11
… to trace-build

This fixes two issues introduced in PR #90397:

1. **Don't block SSG on Turbopack shutdown**: Previously, `workerMain()`
   awaited the shutdown promise before returning, which blocked SSG from
   starting until Turbopack persistence completed. Now `workerMain` returns
   immediately, and trace event collection happens in `waitForShutdown()`
   which is called asynchronously after the build. This allows SSG and
   persistence to run in parallel.

2. **Add Turbopack persistence spans to trace-build allowlist**: Added
   `turbopack-build-events`, `turbopack-persistence`, and
   `turbopack-compaction` to the trace-build allowlist so these spans
   are captured in `.next/trace-build`. Enabled persistent caching in
   the trace-build test fixture to ensure these spans are emitted.

Changes:
- Move trace event collection from `workerMain` to `waitForShutdown`
- Record trace events in parent process after shutdown completes
- Add Turbopack span names to to-json-build allowlist
- Enable turbopackFileSystemCacheForBuild in trace-build test
- Update test snapshot to include turbopack-build-events

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…_IGNORE_DIRTY in test

- After project.shutdown() resolves, yield to the event loop via
  setTimeout so the JS async iterator drains all buffered
  persistence/compaction TraceEvents before getTraceEvents() is called
  in waitForShutdown.
- Set TURBO_ENGINE_IGNORE_DIRTY=1 in the trace-build test so persistent
  caching works in dirty git repos (test directories are always dirty).
- Update the turbopack snapshot to expect turbopack-compaction and
  turbopack-persistence spans.
Replace the setTimeout(100) hack with explicit iterator lifecycle
management:

- Expose stop() on backgroundLogCompilationEvents return value that
  calls iterator.return() to close the async iterator
- After project.shutdown(), call stop() then await the promise to
  drain buffered events before getTraceEvents()
- Signal subscription end from Rust when the receiver loop exits
  (defense in depth: iterator self-closes even without stop())
- Update return type to Promise<void> & { stop(): void }
- Fix TURBO_ENGINE_IGNORE_DIRTY comment and remove inaccurate note
  about persistence spans
- Drop redundant comment in workerMain
The Rust-side subscription close signal allows the JS iterator to
properly drain all compilation events after shutdown, including the
persistence span that was previously missed due to timing.
Add a production-only test that reads the trace-build file and verifies
that static-generation starts before turbopack-persistence finishes.
This ensures SSG is not blocked on the Turbopack shutdown promise.

Uses the startTime field (Date.now() epoch ms) which is comparable
across the worker and parent processes.
The timing-based assertion (startTime comparison) was incorrect:
for small test fixtures, persistence finishes before SSG starts
because the build pipeline has many steps between turbopackBuild()
returning and static-generation beginning.

Replace with a structural assertion that verifies turbopack-persistence
is not an ancestor of static-generation in the span tree, proving
they are independent operations. The existence of both spans in
trace-build already proves the fix works: persistence proves the
iterator was properly drained, and static-generation proves SSG
ran to completion.
Replace the custom stop() method on the backgroundLogCompilationEvents
promise with standard AbortSignal-based cancellation. The caller creates
an AbortController and passes its signal; aborting the signal immediately
closes the async iterator via an abort event listener, fixing the
previous limitation where abort only took effect on the next event.
…s loop

The abort listener already calls iterator.return() which breaks the
for-await loop, making the explicit aborted check unnecessary.
…path cleanup

- Move duplicated parseTraceFile/parseTraceEvents into test/lib/parse-trace-file.ts
  and update all 5 test files to use the shared utility.
- Fix error path in turbopackBuild() to abort the compilation events
  iterator and drain it before shutting down the project, preventing a
  leaked subscription on build failure.
- Use relative import paths for the shared parse-trace-file utility
  instead of bare specifiers that TypeScript can't resolve.
- Rename compilationEventsController to shutdownController (thread 7).
- Improve shutdown comment to explain it's the last chance to capture
  events (thread 8).
- Fix error path ordering: shutdown before abort so final events are
  emitted before the iterator closes (thread 9).
- Remove the SSG/persistence overlap assertion test since it asserts
  a static condition (thread 10).
sokra and others added 2 commits March 15, 2026 15:11
…events are captured

The telemetry global was set after installBindings, so when SWC loading
failed, eventSwcLoadFailure() found no telemetry instance and silently
dropped the NEXT_SWC_LOAD_FAILURE event. Move distDir computation and
telemetry initialization before installBindings to fix this.

Co-Authored-By: Claude <noreply@anthropic.com>
The Telemetry constructor creates distDir/cache in CI environments via
getStorageDirectory(). Moving telemetry init before installBindings
(previous commit) inadvertently caused getCacheDir's "no-cache" warning
to be suppressed because the cache dir already existed when checked.

Co-Authored-By: Claude <noreply@anthropic.com>
@lukesandberg lukesandberg force-pushed the dont_block_ssg_on_telemetry branch from c66ecad to 77ececa Compare March 15, 2026 22:12
Copy link
Contributor Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@lukesandberg lukesandberg merged commit 2e4f08c into canary Mar 16, 2026
285 of 288 checks passed
Copy link
Contributor Author

Merge activity

@lukesandberg lukesandberg deleted the dont_block_ssg_on_telemetry branch March 16, 2026 06:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

created-by: Turbopack team PRs by the Turbopack team. tests Turbopack Related to Turbopack with Next.js. type: next

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants